package at.ac.tuwien.mg.wh;

import java.util.ArrayList;
import java.util.List;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.hibernate.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import at.ac.tuwien.mg.wh.dto.DescribedItemDTO;
import at.ac.tuwien.mg.wh.dto.ItemDTO;
import at.ac.tuwien.mg.wh.exception.WarehouseException;
import at.ac.tuwien.mg.wh.model.Item;
import at.ac.tuwien.mg.wh.model.ItemState;
import at.ac.tuwien.mg.wh.model.ItemUser;
import at.ac.tuwien.mg.wh.model.User;


@Stateless
public class WarehouseBean implements WarehouseBeanRemote {

	private final Logger log = LoggerFactory.getLogger(getClass());
    
    @PersistenceContext(unitName="warehouse")
    private EntityManager em;

    
	@Override
	public void newItems(List<DescribedItemDTO> items){

			for(DescribedItemDTO temp : items){
					em.persist(new Item(
							temp.getCatalogId(),
							temp.getName(),
							temp.getDescription(),
							temp.getCategory(),
							temp.getNumber()));
			}
			
			log.debug("newItems(items= '{}')", items);
	}


	@Override
	public void addItems(List<ItemDTO> items) throws WarehouseException {

			for(ItemDTO one : items){
				Item stocItem = getItem(one.getCatalogId());
				stocItem.setNumber(stocItem.getNumber()+one.getNumber());
				em.persist(stocItem);
			}
			
			log.debug("addItems(items= '{}')", items);
	}
	
	@Override
	public void addItems(List<ItemDTO> items, Long userId) throws WarehouseException {

			addItems(items);
			bookItems(items, userId);
	}


	@Override
	public void removeItems(List<ItemDTO> items, Long userId) throws WarehouseException {

		Item stocItem;
		
			for(ItemDTO one : items){
				
				stocItem = getItem(one.getCatalogId());
				
				// if booked under the userId, take what is booked
				if(userId != null){
					for(ItemUser iu: stocItem.getItemUsers()){
						if(iu.getState().equals(ItemState.BOOKED)
								&& iu.getUser().getUserId().equals(userId)){
							
							if(iu.getNumber() >= one.getNumber()){
								iu.setNumber(iu.getNumber()-one.getNumber());
								stocItem.setNumber(stocItem.getNumber()-one.getNumber());
								one.setNumber(0);
							}else{
								one.setNumber(one.getNumber()-iu.getNumber());
								stocItem.setNumber(stocItem.getNumber()-iu.getNumber());
								iu.setNumber(0);
							}

							if(iu.getNumber().intValue() == 0 ){
								em.remove(iu);
							}else{
								em.persist(iu);
							}
							break;
						}
					}
				}
				
				//try to take the rest from the free stuff
				if( freeItemsNr(one.getCatalogId()) < one.getNumber()){
					throw new WarehouseException("Not enough pieces of item= '"+one+"' on stock.");
				}
				stocItem.setNumber(stocItem.getNumber()-one.getNumber());				
				em.persist(stocItem);
			}
			deleteUserIfNoOtherUserItems(userId);
			
			log.debug("removeItems( items= '{}', userId= '{}')", items, userId);
	}


	@Override
	public List<ItemDTO> getFreeItems(List<Long> componentIds) throws WarehouseException {

		Integer result;
		List<ItemDTO> list = new ArrayList<ItemDTO>();

		for (Long one : componentIds) {
			result = freeItemsNr(one);
			list.add(new ItemDTO(one, result));
		}
		
		log.debug("getFreeItems( componentIds= '{}')", componentIds);
		return list;
	}


	@Override
	public void bookItems(List<ItemDTO> items, Long userId) throws WarehouseException {
		
		Integer free;
		ItemUser booking;
		User user;
		
		Session session = (Session) em.getDelegate();

			for(ItemDTO one : items){

				//check if free
				free = freeItemsNr(one.getCatalogId());		
				if(free < one.getNumber()){
					throw new WarehouseException("Not enough free items on stock");
				}

				//book
				booking = getBooking(one.getCatalogId(), userId);
				
				if(booking == null){ // no booking in DB

					user = (User) session.get(User.class, userId);
					
					if(user == null){ // no user, we need one
						user = new User(userId);
						em.persist(user);
					}
					
					booking =  new ItemUser(getItem(one.getCatalogId()), user, one.getNumber());
						
				}else{
					booking.setNumber(booking.getNumber()+one.getNumber());
				}
				
				em.persist(booking);
			}
			
			log.debug("bookItems( items= '{}', userId= '{}')", items, userId);
		
	}


	@Override
	public void cancelBookedItems(List<ItemDTO> items, Long userId) throws WarehouseException {

		ItemUser booking;
			for(ItemDTO one : items){
				booking = getBooking(one.getCatalogId(), userId);
				booking.setNumber(booking.getNumber()-one.getNumber());			
				em.persist(booking);
				
			}
			deleteUserIfNoOtherUserItems(userId);	
			
			log.debug("cancelBookedItems( items= '{}', userId= '{}')", items, userId);
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public void cleanDdData() {
		
		Session session = (Session) em.getDelegate();
		
		for(ItemUser ui : (List<ItemUser>) session.createCriteria(ItemUser.class).list()){
			session.delete(ui);
		}
		for(Item ui : (List<Item>) session.createCriteria(Item.class).list()){
			session.delete(ui);
		}
		for(User ui : (List<User>) session.createCriteria(User.class).list()){
			session.delete(ui);
		}
		log.debug("cleanDdData()");
		
	}
	
	/* -------------------      HELPERS       -------------------*/
	
	
	
	private Item getItem(Long catalogId){
		return(Item) ((Session) em.getDelegate()).get(Item.class, catalogId);
	}
	
	private Integer freeItemsNr(Long catalogId) throws WarehouseException{
		Long result =  (Long) em.createNamedQuery("numberOfFreePieces")
				.setParameter("id", catalogId)
				.getSingleResult();
		if(result == null){
			return getItem(catalogId).getNumber();
		}else{
			return result.intValue();
		}
	}
	
	@SuppressWarnings("unchecked")
	private ItemUser getBooking(Long catalogId, Long uid) throws WarehouseException{
		List<ItemUser> booking = em.createNamedQuery("bookingForItemAndUser")
				.setParameter("id", catalogId)
				.setParameter("uid", uid)
				.getResultList();
		
		if(booking.isEmpty()){
			return null;
		}else if(booking.size() == 1){
			return booking.get(0);
		}else{
			throw new WarehouseException("There are more bookings of item= '"+booking.get(0).getItem().getCatalogId()+"' by user= '"+booking.get(0).getUser().getUserId()+"'");
		}
	}

	private void deleteUserIfNoOtherUserItems(Long uid){
		boolean remove = true;
		if(uid == null){
			return;
		}
		User user = (User) ((Session) em.getDelegate()).get(User.class, uid);
		
		for(ItemUser ui: user.getItemUsers()){
			if(ui.getNumber() > 0){
				remove = false;
			}
		}
		
		if(remove){
			em.remove(user);
		}
	}

}
